package cn.demoncat.util.sec;

import cn.demoncat.util.exception.BizRuntimeException;
import cn.demoncat.util.lang.StringUtil;
import org.apache.commons.lang3.ArrayUtils;

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA 非对称加密(公钥加密私钥解密，私钥加密公钥解密) 
 * <br/>	   
 * 注意：RSA计算消耗大，所以通常用来加密AES密钥，然后用AES去加密数据
 * 
 * @author 延晓磊
 *
 * @since 2016年8月11日
 */
public class RSAUtil {

	/**
	 * 数据分隔符
	 */
	public static final String SEPARATOR = "@@@";
	/**
	 * 密钥长度
	 */
	private static final int KEY_SIZE = 1024;
	/**
	 * 加密算法
	 */
	private static final String ALGORITHM = "RSA";
	/**
	 * 密码器填充模式
	 */
	private static final String PADDING = "RSA/ECB/PKCS1Padding";
	/**
	 * 加密分块大小
	 */
	private static final int ENCRYPT_BLOCK_SIZE = KEY_SIZE / 8 - 11;
	/**
	 * 解密分块大小
	 */
	private static final int DECRYPT_BLOCK_SIZE = KEY_SIZE / 8;

	/**
	 * 获取密钥对(私钥保存在服务端，公钥颁发给客户端，双方通过RSA加密解密)
	 * 
	 * @return [公钥:216，私钥:848]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	public static RsaKey getKeys(){
		// 生成密钥对
		KeyPair keyPair = getKeyPair();
		// 返回公钥私钥
		RsaKey keys = new RsaKey();
		keys.publicKey = getPublicKey(keyPair);
		keys.privateKey = getPrivateKey(keyPair);
		return keys;
	}

	/**
	 * 公钥加密
	 * 
	 * @param data	源数据
	 * @param pub	公钥
	 * @return 加密数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	public static String encryptPub(String data,String pub){
		return encrypt(StringUtil.getBytes(data), getPublicKey(pub));
	}
 
	/**
	 * 私钥解密
	 * 
	 * @param data	加密数据
	 * @param pri	私钥
	 * @return 源数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	public static String decryptPri(String data,String pri){
		return StringUtil.getStr(dncrypt(data, getPrivateKey(pri)));
	}
 
	/**
	 * 私钥加密
	 * 
	 * @param data 源数据
	 * @param pri  私钥
	 * @return 加密数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	public static String encryptPri(String data,String pri){
		return encrypt(StringUtil.getBytes(data), getPrivateKey(pri));
	}
 
	/**
	 * 公钥解密
	 * 
	 * @param data	加密数据
	 * @param pub	公钥
	 * @return 源数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	public static String decryptPub(String data,String pub){
		return StringUtil.getStr(dncrypt(data, getPublicKey(pub)));
	}
	
	/**
	 * 发送数据：RSA公钥加密 + AES加密 + MD5签名
	 * 
	 * @param data	源数据
	 * @param pub	公钥
	 * @return 加密数据：RAS(aesKey,pubKey) + @@ + AES(MD5(data) + @@ + data, aesKey)
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年11月1日
	 */
	public static String sendDataByPub(String data, String pub) {
		// MD5签名数据
		String md5 = Md5Util.digest(data);
		// 生成AES随机密钥
		String aes = AESUtil.getKey();
		// 使用AES加密"签名+数据"：AES(MD5(data) + @@ + data, aesKey)
		String result = AESUtil.encode(md5 + RSAUtil.SEPARATOR + data, aes);
		// 使用RAS公钥加密AES密钥：RAS(aesKey,pubKey)
		String rsa = RSAUtil.encryptPub(aes, pub);
		// 返回结果：RAS(aesKey,pubKey) + @@ + AES(MD5(data) + @@ + data, aesKey)
		return rsa + RSAUtil.SEPARATOR + result;
	}
	
	/**
	 * 发送数据：RSA私钥加密 + AES加密 + MD5签名
	 * 
	 * @param data	源数据
	 * @param pri	私钥
	 * @return 加密数据：RAS(aesKey,priKey) + @@ + AES(MD5(data) + @@ + data, aesKey)
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年11月1日
	 */
	public static String sendDataByPri(String data, String pri) {
		// MD5签名数据
		String md5 = Md5Util.digest(data);
		// 生成AES随机密钥
		String aes = AESUtil.getKey();
		// 使用AES加密"签名+数据"：AES(MD5(data) + @@ + data, aesKey)
		String result = AESUtil.encode(md5 + RSAUtil.SEPARATOR + data, aes);
		// 使用RAS私钥加密AES密钥：RAS(aesKey,priKey)
		String rsa = RSAUtil.encryptPri(aes, pri);
		// 返回结果：RAS(aesKey,priKey) + @@ + AES(MD5(data) + @@ + data, aesKey)
		return rsa + RSAUtil.SEPARATOR + result;
	}

	/**
	 * 接收数据：RSA公钥解密 + AES解密 + MD5签名
	 * 
	 * @param data	私钥加密数据：RAS(aesKey,priKey) + @@ + AES(MD5(data) + @@ + data, aesKey)
	 * @param pub	公钥
	 * @return 源数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年11月1日
	 */
	public static String readDataByPub(String data, String pub) {
		// 分割数据：RAS(aesKey,priKey) + @@ + AES(……)
		String[] arr = data.split(RSAUtil.SEPARATOR,2);
		// 使用公钥解密AES密钥
		String aes = RSAUtil.decryptPub(arr[0], pub);
		// 使用AES解密"签名+数据"
		data = AESUtil.decode(arr[1], aes);
		// 分割数据：MD5(data) + @@ + data
		arr = data.split(RSAUtil.SEPARATOR,2);
		// MD5签名数据
		String md5 = Md5Util.digest(arr[1]);
		// 判断签名
		if (md5.equals(arr[0])) {
			return arr[1];
		}else {
			throw new BizRuntimeException("解密数据失败");
		}
	}
 
	/**
	 * 接收数据：RSA私钥解密 + AES解密 + MD5签名
	 * 
	 * @param data	公钥加密数据：RAS(aesKey,pubKey) + @@ + AES(MD5(data) + @@ + data, aesKey)
	 * @param pri	私钥
	 * @return 源数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年11月1日
	 */
	public static String readDataByPri(String data, String pri) {
		// 分割数据：RAS(aesKey,pubKey) + @@ + AES(……)
		String[] arr = data.split(RSAUtil.SEPARATOR,2);
		// 使用私钥解密AES密钥
		String aes = RSAUtil.decryptPri(arr[0], pri);
		// 使用AES解密"签名+数据"
		data = AESUtil.decode(arr[1], aes);
		// 分割数据：MD5(data) + @@ + data
		arr = data.split(RSAUtil.SEPARATOR,2);
		// MD5签名数据
		String md5 = Md5Util.digest(arr[1]);
		// 判断签名
		if (md5.equals(arr[0])) {
			return arr[1];
		}else {
			throw new BizRuntimeException("解密数据失败");
		}
	}


	
	/**
	 * 加密
	 * 
	 * @param data	源数据
	 * @param key	密钥
	 * @return 加密数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	private static String encrypt(byte[] data, Key key) {
		// RSA算法每次加密的字节数，不能超过密钥的长度值(128)减去11；解密时不能超过密钥长度，所以加密解密时都要使用分段方式
		if (data.length <= ENCRYPT_BLOCK_SIZE) {
			return Base64Util.encode(encode(data,key));
		} else {
			byte[] buffer = null;
			byte[] blockBytes;
			int index = ((data.length - 1) / ENCRYPT_BLOCK_SIZE) + 1;
			for (int i = 0; i < index; i++) {
				int startIndex = i * ENCRYPT_BLOCK_SIZE;
				int endIndex = startIndex + ENCRYPT_BLOCK_SIZE;
				blockBytes = ArrayUtils.subarray(data, startIndex, endIndex);
				if (buffer == null) {
					buffer = encode(blockBytes, key);
				} else {
					buffer = ArrayUtils.addAll(buffer, encode(blockBytes, key));
				}
			}
			//编码加密数据
			return Base64Util.encode(buffer);
		}
	}
 
	/**
	 * 加密
	 * 
	 * @param data	源数据(长度<128)
	 * @param key	密钥
	 * @return 加密数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	private static byte[] encode(byte[] data, Key key) {
		try {
			Cipher cipher = Cipher.getInstance(PADDING,SecurityUtil.PROVIDER);
			cipher.init(Cipher.ENCRYPT_MODE, key);
			return cipher.doFinal(data);
		} catch (Exception e) {
			throw new RuntimeException("RSA加密失败",e);
		}
	}
 
	/**
	 * 解密
	 * 
	 * @param data	加密数据
	 * @param key	密钥
	 * @return 源数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	private static byte[] dncrypt(String data, Key key) {
		// 解码加密数据
		byte[] datas = Base64Util.decode(data);
		// RSA算法每次加密的字节数，不能超过密钥的长度值(128)减去11；解密时不能超过密钥长度，所以加密解密时都要使用分段方式
		if (datas.length <= DECRYPT_BLOCK_SIZE) {
			return decode(datas,key);
		} else {
			byte[] buffer = null;
			byte[] blockBytes;
			int index = ((datas.length - 1) / DECRYPT_BLOCK_SIZE) + 1;
			for (int i = 0; i < index; i++) {
				int startIndex = i * DECRYPT_BLOCK_SIZE;
				int endIndex = startIndex + DECRYPT_BLOCK_SIZE;
				blockBytes = ArrayUtils.subarray(datas, startIndex, Math.min(endIndex, datas.length));
				if (buffer == null) {
					buffer = decode(blockBytes, key);
				} else {
					buffer = ArrayUtils.addAll(buffer, decode(blockBytes, key));
				}
			}
			return buffer;
		}
	}
 
	/**
	 * 解密
	 * 
	 * @param data	加密数据(长度<128)
	 * @param key	密钥
	 * @return 源数据
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	private static byte[] decode(byte[] data, Key key) {
		try {
			Cipher cipher = Cipher.getInstance(PADDING,SecurityUtil.PROVIDER);
			cipher.init(Cipher.DECRYPT_MODE, key);
			return cipher.doFinal(data);
		} catch (Exception e) {
			throw new RuntimeException("RSA解密失败",e);
		}
	}
	
	/**
	 * 获取密钥对
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	private static KeyPair getKeyPair() {
		try {
			// 实例化密钥对生成器
			KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM, SecurityUtil.PROVIDER);
			// 初始化密钥对生成器
			kpg.initialize(KEY_SIZE);
			// 生成密钥对
			return kpg.generateKeyPair();
		} catch (Exception e) {
			throw new RuntimeException("获取RSA密钥对失败",e);
		}
	}
 
	/**
	 * 获取公钥
	 * 
	 * @param keyPair	密钥对
	 * @return 公钥KEY
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	private static String getPublicKey(KeyPair keyPair) {
		return Base64Util.encode(keyPair.getPublic().getEncoded());
	}
 
	/**
	 * 获取私钥
	 * 
	 * @param keyPair 密钥对
	 * @return 私钥KEY
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	private static String getPrivateKey(KeyPair keyPair) {
		return Base64Util.encode(keyPair.getPrivate().getEncoded());
	}
 
	/**
	 * 转换公钥
	 * 
	 * @param publicKey	公钥
	 * @return 公钥KEY
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	private static PublicKey getPublicKey(String publicKey) {
		// 转换公钥材料
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Util.decode(publicKey));
		try {
			// 实例化密钥工厂
			KeyFactory kf = KeyFactory.getInstance(ALGORITHM,SecurityUtil.PROVIDER);
			// 获取公钥
			return kf.generatePublic(keySpec);
		} catch (Exception e) {
			throw new RuntimeException("转换RSA公钥KEY失败",e);
		}
	}
 
	/**
	 * 转换私钥
	 * 
	 * @param privateKey	私钥
	 * @return	私钥KEY
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年1月22日
	 */
	private static PrivateKey getPrivateKey(String privateKey) {
		// 转换私钥材料
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Util.decode(privateKey));
		try {
			// 实例化密钥工厂
			KeyFactory kf = KeyFactory.getInstance(ALGORITHM,SecurityUtil.PROVIDER);
			// 获取私钥
			return kf.generatePrivate(keySpec);
		} catch (Exception e) {
			throw new RuntimeException("转换RSA私钥KEY失败",e);
		}
	}
	
	/**
	 * RSA密钥
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年11月1日
	 */
	public static final class RsaKey{
		//公钥:216
		private String publicKey;
		//私钥:848
		private String privateKey;
		
		/**
		 * 公钥:216
		 * 
		 * @return
		 * 
		 * @author 延晓磊
		 *
		 * @since 2019年11月1日
		 */
		public String getPublicKey() {
			return publicKey;
		}
		
		/**
		 * 私钥:848
		 * 
		 * @return
		 * 
		 * @author 延晓磊
		 *
		 * @since 2019年11月1日
		 */
		public String getPrivateKey() {
			return privateKey;
		}
	}
	
}
